package com.example.servicegateway.sentinel;

import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.nacos.api.PropertyKeyConst;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * sentinel 配置类
 * 1.实现通过nacos获取api分组，限流，熔断规则
 * 2.使用sentinel为网关进行限流，熔断操作，必须通过SPI 接口进行扩展，使用GatewaySlotChainBuilder创建slot chain，(1.7.2不需要配置GatewaySlotChainBuilder创建slot)
 * 在resource/META-INF/services目录下进行SPI配置
 * 3.启动-Dcsp.sentinel.app.type=1，这样才能sentinel dashboard显示网关api分组配置
 *
 * @author K
 */
@Slf4j
//@Configuration
public class SentinelRuleConfig {

    @Resource
    private NacosConfigProperties nacosConfigProperties;

    @Value("${spring.application.name}")
    private String applicationName;

    @PostConstruct
    public void loadRule() {
        Properties properties = new Properties();
        properties.put(PropertyKeyConst.SERVER_ADDR, nacosConfigProperties.getServerAddr());
        properties.put(PropertyKeyConst.NAMESPACE, nacosConfigProperties.getNamespace());
        final String groupId = "SENTINEL_GROUP";
        final String apiDataId = String.format("%s-api-rules", applicationName);
        final String degradeDataId = String.format("%s-degrade-rules", applicationName);
        final String flowDataId = String.format("%s-flow-rules", applicationName);

        // api规则注册
        ReadableDataSource<String, Set<ApiDefinition>> apiRuleDataSource =
                new NacosDataSource<>(properties, groupId, apiDataId, apiConverter);
        GatewayApiDefinitionManager.register2Property(apiRuleDataSource.getProperty());

        // 降级规则
        ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource =
                new NacosDataSource<>(properties, groupId, degradeDataId,
                        source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {
                        }));
        DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());

        // 网关限流规则
        ReadableDataSource<String, Set<GatewayFlowRule>> gatewayFlowRuleDataSource =
                new NacosDataSource<>(properties, groupId, flowDataId,
                        source -> JSON.parseObject(source, new TypeReference<Set<GatewayFlowRule>>() {
                        }));
        GatewayRuleManager.register2Property(gatewayFlowRuleDataSource.getProperty());

        // 限流，降级异常信息
        GatewayCallbackManager.setBlockHandler(new SentinelBlockRequestHandler());
    }

    private final Converter<String, Set<ApiDefinition>> apiConverter = (source) -> {
        List<Map<String, Object>> list = JSON.parseObject(source, new TypeReference<List<Map<String, Object>>>() {
        });

        Set<ApiDefinition> apiDefinitionSet = new HashSet<>();
        for (Map<String, Object> map : list) {
            Set<ApiPredicateItem> set = new HashSet<>();
            List<JSONObject> jsonObjects = (List<JSONObject>) map.get("predicateItems");
            if (CollectionUtils.isEmpty(jsonObjects)) {
                continue;
            }

            for (JSONObject o : jsonObjects) {
                ApiPathPredicateItem item = new ApiPathPredicateItem();
                item.setPattern(o.getString("pattern"));
                item.setMatchStrategy(o.getInteger("matchStrategy"));

                set.add(item);
            }

            ApiDefinition apiDefinition = new ApiDefinition();
            apiDefinition.setApiName(map.get("apiName").toString());
            apiDefinition.setPredicateItems(set);

            apiDefinitionSet.add(apiDefinition);
        }
        return apiDefinitionSet;
    };
}
